//Examples/encoder.cpp

/*
   This program takes as input an inputfile, k, m, a coding 
   technique, w, and packetsize.  It creates k+m files from 
   the original file so that k of these files are parts of 
   the original file and m of the files are encoded based on 
   the given coding technique. The format of the created files 
   is the file name with "_k#" or "_m#" and then the extension.  
   (For example, inputfile test.txt would yield file "test_k1.txt".)

*/

#include <sys/time.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include "jerasure.h"
#include "reed_sol.h"
#include "galois.h"
#include "bitmatrices.h"

#define NUM_OF_METHODS 8

enum Coding_Technique {Reed_Sol_Van, Reed_Sol_R6_Op, Cauchy_Orig, Cauchy_Good, R6_Min_Density, Gen_RDP, Gen_EVENODD, No_Coding};

char *Methods[NUM_OF_METHODS] = {(char *)"reed_sol_van", (char *)"reed_sol_r6_op", (char *)"cauchy_orig", (char *)"cauchy_good", (char *)"r6_min_density", (char *)"gen_rdp",(char *)"gen_evenodd", (char *)"no_coding"};

/* Global variables for signal handler */
int readins, readin_count;
enum Coding_Technique method;

/* Function prototypes */
int is_prime(int w);
void ctrl_bs_handler(int dummy);

/* Either reads size bytes from a file, or fill ptrs with size random bytes 
   Returns the number of bytes read.
   size must be a multiple of 8.
*/
int jfread(void *ptr, int size, FILE *stream)
{
	int nd;
	int i;
	uint64_t *li;
	if (stream != NULL) return fread(ptr, 1, size, stream);

	nd = size/8;
	li = (uint64_t *) ptr;
	for (i = 0; i < nd; i++){
		li[i] = lrand48();
		li[i] << 8*4;
		li[i] += lrand48();
	}
	return size;
}

void usage(){
	int i;
	fprintf(stderr,  "Usage: ./encoder inputfile K M coding_technique W (packetsize) (buffersize=filesize)\n");
	fprintf(stderr,  "Valid coding_techniques are:\n");
	for(i=0;i<NUM_OF_METHODS;i++){
		fprintf(stderr,"%s\n",Methods[i]);
	}
}

int main (int argc, char **argv) {

	int i, j;		

	FILE *fp, *fp2;				// file pointers
	unsigned char *block;		// padding file
	unsigned long filesize_orig; //the actual sie of the file we are encoding
	unsigned long filesize_new; //the size of the file after padding
	struct stat status;			// finding file size

	enum Coding_Technique tech;	// coding technique (parameter)
	int n, k, m, w; //coding parameters
	unsigned long packets_per_slice;
	unsigned long packetsize;
	unsigned long blocksize; //size of each slice in slices->Ptrs 
	unsigned long buffersize; //amount of the file we read in at once	
	unsigned long total_bytes_read;
	unsigned long extra;

	/* Jerasure variables */
	JER_Slices * slices;
	JER_Gen_T *g;

	/* Creation of file name variables */
	char temp[5];
	char *s1, *s2;
	char *fname;
	int md;
	char *curdir;

	/* Timing variables */
	struct timeval t_total_start, t_total_end, t_encode_start, t_encode_end;
	double time_encode; //the time spent in the Encode() function
	double time_total; //the total time (includes reading and writing from/to disk)
	double dbl_blocksize;
	double MB_encoded; 

	bool bll_bit_coding; //true if encoding with a bitmatrix

	time_encode = 0;
	time_total = 0;
	gettimeofday(&t_total_start, NULL);

	signal(SIGQUIT, ctrl_bs_handler);

	/* Error check Arguments*/
	if (argc < 6) {
		//not enough arguments
		usage();
		exit(0);
	}

	/* Conversion of parameters and error checking */	
	if (sscanf(argv[2], "%d", &k) == 0 || k <= 0) {
		fprintf(stderr,  "Invalid value for k\n");
		exit(0);
	}
	if (sscanf(argv[3], "%d", &m) == 0 || m < 0) {
		fprintf(stderr,  "Invalid value for m\n");
		exit(0);
	}
	if (sscanf(argv[5],"%d", &w) == 0 || w <= 0) {
		fprintf(stderr,  "Invalid value for w.\n");
		exit(0);
	}

	/* Setting of coding technique and error checking */
	bll_bit_coding = true;
	if (strcmp(argv[4], "no_coding") == 0) {
		bll_bit_coding = false;
		tech = No_Coding;
	}
	else if (strcmp(argv[4], "reed_sol_van") == 0) {
		bll_bit_coding = false;
		tech = Reed_Sol_Van;
		if (w != 8 && w != 16 && w != 32) {
			fprintf(stderr,  "w must be one of {8, 16, 32}\n");
			exit(0);
		}
	}
	else if (strcmp(argv[4], "reed_sol_r6_op") == 0) {
		bll_bit_coding = false;
		tech = Reed_Sol_R6_Op;
		if (m != 2) {
			fprintf(stderr,  "m must be equal to 2\n");
			exit(0);
		}
		if (w != 8 && w != 16 && w != 32) {
			fprintf(stderr,  "w must be one of {8, 16, 32}\n");
			exit(0);
		}
	}
	else if (strcmp(argv[4], "cauchy_orig") == 0) {
		tech = Cauchy_Orig;
	}
	else if (strcmp(argv[4], "cauchy_good") == 0) {
		tech = Cauchy_Good;
	}
	else if (strcmp(argv[4], "r6_min_density") == 0) {
		tech = R6_Min_Density;
		if (m != 2) {
			fprintf(stderr,  "m must be equal to 2\n");
			exit(0);
		}
		if (k > w) {
			fprintf(stderr,  "k must be less than or equal to w\n");
			exit(0);
		}
		if(is_prime(w)){
			//liberation
			if (w <= 2 || !is_prime(w)) {
				fprintf(stderr,  "w must be greater than two and w must be prime\n");
				exit(0);
			}
		}else if(is_prime(w+1)){
			//blaum-roth
			if (w <= 2 || !is_prime(w+1)) {
				fprintf(stderr,  "w must be greater than two and w+1 must be prime\n");
				exit(0);
			}
		}else if( w == 8){
			//liber8tion

		}else{
			fprintf(stderr,"Invalid value of w (%d)\n",w);
			fprintf(stderr,"     For liberation, w must be prime\n");
			fprintf(stderr,"     For blaum-roth, (w+1) must be prime\n");
			fprintf(stderr,"     For liber8tion, w must equal 8\n");
			exit(0);
		}
	}else if (strcmp(argv[4], "gen_rdp") == 0) {
		tech = Gen_RDP;
		//rdp
		if (k > w) {
			fprintf(stderr,  "k must be less than or equal to w\n");
			exit(0);
		}
		if(!is_prime(w+1)){
			fprintf(stderr,  "w+1 must be prime\n");
			exit(0);
		}
	}else if (strcmp(argv[4], "gen_evenodd") == 0) {
		tech = Gen_EVENODD;
		//evenodd
		if (k > (w+1)) {
			fprintf(stderr,  "k must be less than or equal to w\n");
			exit(0);
		}
		if(!is_prime(w+1)){
			fprintf(stderr,  "w+1 must be prime\n");
			exit(0);
		}
	}
	else {
		fprintf(stderr,  "Not a valid coding technique.");
		fprintf(stderr,  "\nChoose one of the following coding techniques: \n");
		for(i=0;i<NUM_OF_METHODS;i++){
			fprintf(stderr,"%s\n",Methods[i]);
		}
		exit(0);
	}
	/* Set global variable method for signal handler */
	method = tech;

	//read the packetsize
	//it is not required as an input argument for non-bitmatrix encoding
	if(bll_bit_coding == true){
		//bitmatrix based coding, requires a packetsize
		if (argc >= 7) {
			if (sscanf(argv[6], "%lu", &packetsize) == 0 || packetsize %8 != 0 || packetsize == 0) {
				fprintf(stderr,  "packetsize must be a multiple of 8\n");
				exit(0);
			}
		}else{
			fprintf(stderr,"Error: encoding with a bit-matrix requires a packetsize\n");
			exit(0);
		}
	}

	//read the buffersize
	if (argc >= 8) {
		if (sscanf(argv[7], "%lu", &buffersize) == 0 || buffersize < 0) {
			fprintf(stderr, "Invalid value for buffersize\n");
			exit(0);
		}
	}else{
		buffersize = 0;
	}

	/* Get current working directory for construction of file names */
	curdir = (char*)malloc(sizeof(char)*1000);	
	if(getcwd(curdir, 1000) == NULL){
		fprintf(stderr,"Error: getcwd()\n");
	}

	/* Get the file size */
	if (argv[1][0] != '-') {

		/* Open file and error check */
		fp = fopen(argv[1], "rb");
		if (fp == NULL) {
			fprintf(stderr,  "Unable to open file.\n");
			exit(0);
		}

		/* Create Coding directory */
		i = mkdir("Coding", S_IRWXU);
		if (i == -1 && errno != EEXIST) {
			fprintf(stderr, "Unable to create Coding directory.\n");
			exit(0);
		}

		/* Determine original size of file */
		stat(argv[1], &status);	
		filesize_orig = status.st_size;
	} else {
		if (sscanf(argv[1]+1, "%lu", &filesize_orig) != 1 || filesize_orig <= 0) {
			fprintf(stderr, "Files starting with '-' should be sizes for randomly created input\n");
			exit(1);
		}
		fp = NULL;
		srand48(time(0));
	}

	if(buffersize == 0){
		//the user did not specify how much of the file to read in at once
		//read it all at once
		buffersize = filesize_orig;
	}else if(buffersize > filesize_orig){
		buffersize = filesize_orig;
	}
	blocksize = buffersize/k;
	if(blocksize == 0) blocksize = 8;

	//adjust the blocksize to be valid
	if(bll_bit_coding == false){
		//blocksize must be a multiple of 8
		while(blocksize%8 != 0){
			blocksize++;
		}
		packetsize = blocksize;
	}else{
		//packetsize has already been checked to be a multiple of 8
		//blocksize must be a multiple of w*packetsize
		while(blocksize % (w*packetsize) != 0){
			blocksize++;
		}			
	}
	//now buffersize is valid
	buffersize = blocksize *k;

	//change the filesize to be a multiple of buffersize
	if(buffersize >= filesize_orig){
		//make one read of buffersize bytes
		filesize_new = buffersize;
	}else{
		//multiple reads required
		if(filesize_orig % buffersize !=0){
			filesize_new = ((filesize_orig/buffersize)+1)*buffersize;
		}else{
			filesize_new = filesize_orig;
		}
	}

	dbl_blocksize = blocksize;
	block = (unsigned char *)malloc(sizeof(unsigned char)*buffersize);
	readins = filesize_new/buffersize;

	/* Break inputfile name into the filename and extension */	
	s1 = (char*)malloc(sizeof(char)*(strlen(argv[1])+10));
	s2 = strrchr(argv[1], '/');
	if (s2 != NULL) {
		s2++;
		strcpy(s1, s2);
	}
	else {
		strcpy(s1, argv[1]);
	}
	s2 = strchr(s1, '.');
	if (s2 != NULL) {
		*s2 = '\0';
	}
	fname = strchr(argv[1], '.');
	s2 = (char*)malloc(sizeof(char)*(strlen(argv[1])+5));
	if (fname != NULL) {
		strcpy(s2, fname);
	}

	/* Allocate for full file name */
	fname = (char*)malloc(sizeof(char)*(strlen(argv[1])+strlen(curdir)+20));
	sprintf(temp, "%d", k);
	md = strlen(temp);

	/* Create generator */ 
	switch(tech) {
		case No_Coding:
			break;
		case Reed_Sol_Van:
			g = RS_Classic_Generator(k,m,w); 
			break;
		case Cauchy_Orig:
			g = CRS_Generator(k,m,w);
			CRS_Convert_To_Bitmatrix(*g);
			//smart == 1
			g->Create_Encode_Schedule(1);
			break;
		case Cauchy_Good:
			g = CRS_Good_Generator_Bitmatrix(k,m,w);
			//smart == 1
			g->Create_Encode_Schedule(1);
			break;	
		case Reed_Sol_R6_Op:
			g = RS_R6_Generator(k,w);
			break;	
		case R6_Min_Density:
			g = R6_Min_Density_Generator(k,w);
			//smart == 1
			g->Create_Encode_Schedule(1);
			break;
		case Gen_RDP:
			g = Gen_RDP_Generator(k, m, w);
			break;
		case Gen_EVENODD:
			g = Gen_Evenodd_Generator(k, m, w);
			break;
	}

	if(g == NULL){
		fprintf(stderr,"Error: could not create generator\n");
		exit(1);
	}

	//create slices
	n = k+m;
	packets_per_slice = blocksize / packetsize;
	printf("buffersize: %lu\n",buffersize);
	printf("packetsize: %lu\n",packetsize);
	printf("blocksize: %lu\n",blocksize);
	printf("original filesize: %lu\n",filesize_orig);
	printf("new filesize: %lu\n",filesize_new);
	printf("number of readins: %d\n\n",readins);

	slices = new JER_Slices(n, k, packetsize, packets_per_slice, g);
	/* Set pointers to point to file data */
	for (i = 0; i < k; i++) {
		slices->Ptrs[i] = block+(i*blocksize);
	}
	//allocate space for the m slices of coding data
	for (i = k; i < k+m; i++) {
		slices->Ptrs[i] = (unsigned char *)malloc(sizeof(unsigned char)*blocksize);
	}

	/* Read in data until finished */
	readin_count = 1;
	total_bytes_read = 0;

	while (readin_count <= readins) {
		if (total_bytes_read < filesize_orig && total_bytes_read+buffersize <= filesize_orig) {
			//we have at least buffersize bytes to read from the real file
			total_bytes_read += jfread(block, buffersize, fp);
		}
		else if (total_bytes_read < filesize_orig && total_bytes_read+buffersize > filesize_orig) {
			//a portion of buffersize will be padding
			extra = jfread(block, buffersize, fp);
			for (i = extra; i < buffersize; i++) {
				block[i] = '0';
			}
		}
		else if (total_bytes_read == filesize_orig) {
			//all of buffersize is padding
			for (i = 0; i < buffersize; i++) {
				block[i] = '0';
			}
		}

		/* Encode according to coding method */
		if(tech != No_Coding){
			gettimeofday(&t_encode_start, NULL);
			slices->Encode();
			gettimeofday(&t_encode_end, NULL);
			time_encode += t_encode_end.tv_usec;
			time_encode -= t_encode_start.tv_usec;
			time_encode /= 1000000.0;
			time_encode += t_encode_end.tv_sec;
			time_encode -= t_encode_start.tv_sec;
		}

		/* Write data and encoded data to k+m files */
		for	(i = 1; i <= k; i++) {
				sprintf(fname, "%s/Coding/%s_k%0*d%s", curdir, s1, md, i, s2);
				if (n == 1) {
					fp2 = fopen(fname, "wb");
				}
				else {
					fp2 = fopen(fname, "ab");
				}
				fwrite(slices->Ptrs[i-1], sizeof(char), blocksize, fp2);
				fclose(fp2);
		}
		for	(i = 1; i <= m; i++) {
				sprintf(fname, "%s/Coding/%s_m%0*d%s", curdir, s1, md, i, s2);
				if (n == 1) {
					fp2 = fopen(fname, "wb");
				}
				else {
					fp2 = fopen(fname, "ab");
				}
				fwrite(slices->Ptrs[k+i-1], sizeof(char), blocksize, fp2);
				fclose(fp2);
		}
		readin_count++;
	}

	/* Create metadata file */
	if (fp != NULL) {
		sprintf(fname, "%s/Coding/%s_meta.txt", curdir, s1);
		fp2 = fopen(fname, "wb");
		fprintf(fp2, "%s\n", argv[1]); //filename
		fprintf(fp2, "%lu\n", filesize_orig);
		fprintf(fp2, "%d %d %d %lu %lu\n", k, m, w, packetsize, buffersize);
		fprintf(fp2, "%s\n", argv[4]); //technique name
		fprintf(fp2, "%d\n", tech); //technique number
		fprintf(fp2, "%d\n", readins);
		fclose(fp2);
	}

	/* Free allocated memory */
	for(i=k;i<k+m;i++){
		//free the coding data
		free(slices->Ptrs[i]);
	}
	delete slices;
	delete g;
	free(s2);
	free(s1);
	free(fname);
	free(block);
	free(curdir);
	if(fp != NULL){
		fclose(fp);
	}

	gettimeofday(&t_total_end, NULL);
	time_total += t_total_end.tv_usec;
	time_total -= t_total_start.tv_usec;
	time_total /= 1000000.0;
	time_total += t_total_end.tv_sec;
	time_total -= t_total_start.tv_sec;

	/* Calculate rate in MB/sec and print */
	MB_encoded = dbl_blocksize*m/1024/1024;
	if(tech != No_Coding){
		printf("Encode rate (MB/sec): %0.10f\n", MB_encoded/time_encode);
	}
	printf("Overall rate (MB/sec): %0.10f\n", MB_encoded/time_total);
}

/* is_prime returns 1 if number if prime, 0 if not prime */
int is_prime(int w) {
	int prime55[] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,
		73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,
		181,191,193,197,199,211,223,227,229,233,239,241,251,257};
	int i;
	for (i = 0; i < 55; i++) {
		if (w%prime55[i] == 0) {
			if (w == prime55[i]) return 1;
			else { return 0; }
		}
	}
}

/* Handles ctrl-\ event */
void ctrl_bs_handler(int dummy) {
	time_t mytime;
	mytime = time(0);
	fprintf(stderr, "\n%s\n", ctime(&mytime));
	fprintf(stderr, "You just typed ctrl-\\ in encoder.c.\n");
	fprintf(stderr, "Total number of read ins = %d\n", readins);
	fprintf(stderr, "Current read in: %d\n", readin_count);
	fprintf(stderr, "Method: %s\n\n", Methods[method]);	
	signal(SIGQUIT, ctrl_bs_handler);
}
